//: -version=CPP

import 
	tango.core.Traits,
	tango.io.device.Array ,
	tango.io.Stdout;


struct Tp {
	Tp*		base_ptr 	= null ;
	TypeInfo	ti ;
	char[]	type ;
	
	bool		isObj		= false ;
	bool		isPtr		= false ;
	Fn*		New ;
	Fn*		Del ;
	Tp*[]	properties ;
	Fn*[]		methods ;
	
	char[]	_value ;
	
	char[] dType() {
		return "V8D_Tp_" ~ type ;
	}
	
	char[] _dType() {
		return "struct _" ~ dType ;
	}

	char[] jType() {
		if( isObj ) {
			return "Object";
		}
		return type ;
	}
	
	static Tp*[]	list ;
	
	static Tp* _add2list(T)(char[] type = null){
		Tp* tp	= new Tp ;
		tp.ti		= typeid(T) ;
		if( type is null ){
			tp.type	= T.stringof ;
		}else {
			tp.type	= type.dup ;
		}
		static if( is( T == struct ) ){
			tp.isObj	= true ;
		}
		list	~= tp ;
		return tp ;
	}
	
	static Tp* get(T)(char[] type = null){
		if( list.length is 0 ) {
			_add2list!(bool)("Boolean");
			
			_add2list!(int)("Int32");
			_add2list!(uint)("Uint32");
			
			_add2list!(float)("Number");
			
			_add2list!(char[])("String");
		}
		Tp* _tp	= null ;
		static if( isPointerType!(T) ) {
			T t;
			alias typeof(*t) _T ;
			_tp	= get!(_T) ;
			_tp.isPtr	= true ;
			return  _tp ;
		}
		foreach( o ; list ) {
			if( o.ti == typeid(T) ) {
				o.isPtr	= false ;
				return o ;
			}
		}
		return _add2list!(T)(type) ;
	}
	
	template Warp(alias T) {
		alias typeof(this) This ;
		void*	_jthis ;		
		static assert( is( T* == This ) );
		static assert(_jthis.offsetof is 0 ) ;
		static Tp*	tp ;
		
		static void v8_init() {
			tp		= Tp.get!( T ) ;
			tp.New	= Fn.New!(T.New, Fn.Type.New)(tp);
			tp.Del	= Fn.New!(T.Del, Fn.Type.Del)(tp);
		}
		
		static void v8_method(alias F)(char[] name = null ){
			assert(tp !is null, "you need run v8_init first");
			Fn* fn	= Fn.New!(F, Fn.Type.Method)(tp) ;
			
			T t ;
			const method_name	= (&F).stringof [2..$] ;
			mixin( "auto dg = &t." ~ method_name ~ ";" );
			assert( dg.funcptr == fn.fptr );
		}
		
		
	}
	
	
	static void GenStruct(Tp* tp, Array bu){
		bu(tp._dType)(" {\n");
		bu("	void*	_dobj ;\n");
		bu("} ;\n");
	}
	
	
	static void GenDObjFun(Tp* tp, Array bu_d, Array bu_c){
		assert(tp.New !is null,  tp.type ~ ".New is null" );
		GenDFun(tp.New, bu_d,  bu_c);
		assert(tp.Del !is null );
		GenDFun(tp.Del, bu_d,  bu_c );
		
		foreach( fn ; tp.methods ) {
			GenDFun(fn, bu_d,  bu_c);
		}
	}
	
	static void GenDFun(Fn* fn, Array bu_d, Array bu_c) {
		
		char[] fn_id	= ctfe_i2a(fn.id) ;

		bu_d(fn.ret.tp.dType)(" (")( fn._dName ) (") (") ;
		scope(exit){
			*(cast(char*) &bu_d.slice[$-2])	= ' ' ;
			bu_d("); // id=")( fn_id )("\n");
		}
		
		bu_c("v8::Handle<v8::Value> ")( fn._dName[1..$] )("(const v8::Arguments& args) { // id = ")(  fn_id  )("\n");
		bu_c("	v8::HandleScope scope;\n");
		if( fn.arg_len > 0 ) {
			bu_c("	if( args.Length() < ")( ctfe_i2a(fn.arg_len) )(" ) {\n");
				bu_c(`		return ThrowException(Exception::TypeError(String::New("Must have at least `)( ctfe_i2a(fn.arg_len) )(` args")));`);
			bu_c("\n	}\n");
		}
		
		foreach(int i, va ; fn.args ){
			va.gen(bu_c) ;
			bu_d(va.tp.dType)(", ");
		}
		
		if( !fn.isRefObj ){
			fn.obj.gen(bu_c) ;
		}
		
		fn.ret.gen(bu_c) ;
		
		bu_c("	")( fn.ret.tp.dType)(" ")( fn.ret._dName)(" = ")( fn._dName)(" (") ;
		foreach( va; fn.args ){
			bu_c( va._dName )(", ");
		}
		*(cast(char*) &bu_c.slice[$-2])	= ' ' ;
		bu_c(");\n");
		
		if( fn.ret.isVoid ) {
			
		} else {
			bu_c("	return scope.Close(")( fn.ret._jName )(");\n");
		}
		bu_c("}\n");
		
	}
	
	static void GenCpp() {
		Array bu_header		= new Array(1024, 1024) ;
		Array bu_typedef		= new Array(1024, 1024) ;
		Array bu_struct		= new Array(1024, 1024) ;
		Array bu_function	= new Array(1024, 1024) ;
		Array bu_initialize		= new Array(1024, 1024) ;
		
		foreach( tp ; Tp.list ) {
			if( !tp.isObj ) {
				continue ;
			}
			bu_header(tp._dType)(";\n");
			bu_header("typedef ")(tp._dType)(" *")(tp.dType)(";\n");
			
			GenStruct(tp, bu_struct);
			GenDObjFun(tp, bu_typedef, bu_function);
		}
		
		Array	bu	= new Array(1024, 1024 );
		bu( bu_header.slice ) ("\n") ;
		bu( bu_typedef.slice ) ("\n") ;
		bu( bu_struct.slice ) ("\n") ;
		bu( bu_function.slice ) ("\n") ;
		bu( bu_initialize.slice ) ("\n")  ;
		
		Stdout.formatln("{}", cast(char[]) bu.slice);
	}
}


struct Va {
	Tp*		tp ;
	Fn*		fn ;
	char[]	_id ;
	bool		isPtr ;
	char[]	_dName ;
	char[]	_jName ;
	
	invariant {
		assert(fn !is null);
	}
	
	bool isVoid() {
		return tp.type == "void" ;
	}
	
	bool isRet() { return fn.ret is this ; }
	bool isThis() { return  fn.obj is this ; }
	bool isArg() {
		foreach( ref va ; fn.args ) {
			if( va is this ) {
				return true ;
			}
		}
		return false ;
	}
	
	static Va* New(T)(Fn* fn){
		Tp* tp	= Tp.get!(T) ;
		Va* va	= new Va ;
		va.tp		= tp ;
		va.isPtr	= tp.isPtr ;
		va.fn		= fn ;
		return va ;
	}
	
	void gen(Array bu){
		if( isThis ) {
			_jName	= "arg_this" ;
			_dName	= "_" ~ _jName ;
			
			bu("\tv8::Handle<v8::")( tp.jType)(">	")(_jName)(" = v8::Local<v8::Object>::New(args.Holder()) ; \n");
			bu("\t")( tp.dType)("	")(_dName)("  = ")("static_cast<")( tp.dType)("*>( v8::Handle<v8::External>::Cast( ")(_jName)("->GetInternalField(0) )->Value() ) ; \n");
		} else if( isArg ) {
			_jName	= "arg_" ~ _id ;
			
			bu("\tif( ! args[")(_id)("]->is")(tp.jType)("() ) {\n");
				bu("\t\treturn Exception::Error(String::New(\" args[")(_id)("] must be ")(tp.jType)("\"));\n");
			bu("\t}\n");
			
			if( tp.isObj ) {
				assert( false );
			}else  if( tp.type == "String" ) {
				_dName	= "_" ~_jName ;
				
				bu("\tString::Utf8Value ")(_jName)("( args[")(_id)("]->ToString() ) ;\n") ;
				bu("\t")(tp.dType)("	")(_dName)(" ;\n");
				bu("\t")(_dName)(".len	= ")(_jName)("->length() ;\n");
				bu("\t")(_dName)(".ptr	= *")(_jName)(" ;\n");
			} else {
				_dName	=  "args[" ~ _id ~ "]->To" ~ tp.jType ~ "()" ;
			}
		} else if( isRet ) {
			_jName	= "arg_ret" ;
			_dName	= "_" ~ _jName ;
		}
	}
	
}

struct Fn {
	enum Type{
		New		= 1 ,
		Del		= 1 << 1 ,
		Method	= 1 << 2 ,
		Static	= 1 << 3 ,
	}
	void*	fptr ;
	Type		type ;
	char[]	name ;
	Va*		obj ;
	Va*		ret ;
	Va*[]	args ;
	
	int		id ;
	
	invariant {
		assert(obj is null || obj.tp.isObj);
	}
	
	static Fn*[]	list ;
	
	static Fn* New(alias F, Type type = Type.Static)(Tp* tp = null ){
		foreach( o ; list){
			if( o.fptr == &F ){
				assert(false);
			}
		}
		
		alias ParameterTupleOf!(F)	Args ;
		alias ReturnTypeOf!(F)	Ret ;
		
		Fn* fn	= new Fn ;
		fn.fptr	= &F ;
		fn.id		= list.length ;
		list		~= fn ;
		fn.type	= type ;
		
		fn.name	= (&F).stringof[ 2 .. $ ] ;
		
		fn.ret			= Va.New!(Ret)(fn) ;
		
		fn.args.length	= Args.length ;
		foreach(int i , c ; Args ) {
			fn.args[i]	= Va.New!(c)(fn) ;
			fn.args[i]._id	= ctfe_i2a(i) ;
		}
		
		static if(  type == Type.New ) {
			assert(tp !is null && tp.isObj);
			static assert( isPointerType!(Ret) );
			fn.obj	= fn.ret ;
		} else static if( type == Type.Del ) {
			assert(tp !is null && tp.isObj);
			static assert( Args.length is 1 );
			static assert( isPointerType!(Args[0]) );
			assert( tp.type is fn.args[0].tp.type) ;
			fn.obj	= fn.args[0] ;
		} else static if( type == Type.Method ) {
			assert(tp !is null && tp.isObj);
			fn.obj	= new Va ;
			fn.obj.tp	= tp ;
			fn.obj.fn	= fn ;
			fn.obj.isPtr	= true ;
			tp.methods	~= fn ;
		}
		if( fn.obj ) {
			assert(tp is fn.obj.tp);
			fn.obj._id	= "this" ;
		}
		return fn ;
	}
	
	char[] _dName () {
		if( obj !is null) {
			return "_V8D_" ~ obj.tp.type ~ "_Fn_" ~ name ;
		}
		return "_V8D_Fn_" ~ name ;
	}
	
	bool isMethod(){ return type is Type.Method;}
	bool isNew(){ return type is Type.New;}
	bool isDel(){ return type is Type.Del;}
	
	int arg_len(){
		int len	= args.length ;
		if( isDel ){
			return len - 1 ;
		}
		return len ;
	}
	
	bool isRefObj() {
		return obj is null ||  obj is ret || ( args.length > 0 && obj is args[0] ) ;
	}
}


struct DObj {
	mixin Tp.Warp!(DObj) ob ;
	
	extern(C) static DObj* New(int i){
		return null ;
	}
	
	extern(C) static void Del(DObj* _this){
		
	}
	
	extern(C) void add(int i, char[] j){
		
	}
	
	static void v8_init() {
		ob.v8_init ;
		ob.v8_method!(add)() ;
	}
}


void main() {
	DObj.v8_init ;
	Tp.GenCpp ;
	
}


